﻿#region

using System;
using System.Collections.Generic;
using System.Linq;
using Styx;
using Styx.Pathing;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;

#endregion

namespace FeralKitty
{
    internal static class Unit
    {
        public static HashSet<uint> IgnoreMobs = new HashSet<uint>
            {
                52288,
                // Venomous Effusion (NPC near the snake boss in ZG. Its the green lines on the ground. We want to ignore them.)
                52302, // Venomous Effusion Stalker (Same as above. A dummy unit)
                52320, // Pool of Acid
                52525, // Bloodvenom

                52387, // Cave in stalker - Kilnara
            };

        internal static IEnumerable<WoWUnit> AttackableUnits
        {
            get
            {
                return
                    ObjectManager.GetObjectsOfType<WoWUnit>(true, false)
                                 .Where(
                                     u =>
                                     u.Attackable && u.CanSelect && !u.IsFriendly && !u.IsDead && !u.IsNonCombatPet &&
                                     !u.IsCritter && u.Distance <= 60);
            }
        }

        public static float MeleeRange
        {
            get { return StyxWoW.Me.CurrentTarget.MeleeDistance(); }
        }

        public static IEnumerable<WoWUnit> NearbyUnfriendlyUnits
        {
            get
            {
                return
                    ObjectManager.GetObjectsOfType<WoWUnit>(false, false).Where(
                        p => ValidUnit(p) && p.DistanceSqr <= 40*40).ToList();
            }
        }

        public static IEnumerable<WoWPlayer> NearbyGroupMembers
        {
            get { return GroupMembers.Where(p => p.DistanceSqr <= 30*30).ToList(); }
        }

         


        public static IEnumerable<WoWPlayer> GroupMembers
        {
            get
            {
                // Grab party+raid member + myself GUIDs
                var guids =
                    StyxWoW.Me.GroupInfo.RaidMemberGuids.Union(StyxWoW.Me.GroupInfo.PartyMemberGuids).Union(new[]
                        {StyxWoW.Me.Guid}).Distinct().ToArray();

                return (
                           from p in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true)
                           where p.IsFriendly && guids.Any(g => g == p.Guid)
                           select p).ToList();
            }
        }

        internal static IEnumerable<WoWUnit> NearbyAttackableUnits(WoWPoint fromLocation, double radius)
        {
            var hostile = AttackableUnits;
            var maxDistance = radius*radius;
            return hostile.Where(x => x.Location.DistanceSqr(fromLocation) < maxDistance);
        }

         

        public static bool IsAboveTheGround(WoWUnit u)
        {
            var height = HeightOffTheGround(u);
            if (height == float.MaxValue)
                return false; // make this true if better to assume aerial 

            return height > MeleeRange;
        }

        public static float MeleeDistance(this WoWUnit mob)
        {
            if (mob == null)
                return 0;

            return mob.IsPlayer ? 3.5f : Math.Max(5f, StyxWoW.Me.CombatReach + 1.3333334f + mob.CombatReach);
        }


        public static float HeightOffTheGround(WoWUnit u)
        {
            var unitLoc = new WoWPoint(u.Location.X, u.Location.Y, u.Location.Z);
            var listMeshZ = Navigator.FindHeights(unitLoc.X, unitLoc.Y).Where(h => h <= unitLoc.Z);
            var meshZ = listMeshZ as IList<float> ?? listMeshZ.ToList();
            if (meshZ.Any())
                return unitLoc.Z - meshZ.Max();

            return float.MaxValue;
        }

        public static bool IsCrowdControlled(WoWUnit unit)
        {
            if (StyxWoW.Me.CurrentTarget == null)
                return false;
            var auras = unit.Auras.Values;

            return auras.Any(
                a => a.Spell.Mechanic == WoWSpellMechanic.Banished ||
                     a.Spell.Mechanic == WoWSpellMechanic.Charmed ||
                     a.Spell.Mechanic == WoWSpellMechanic.Horrified ||
                     a.Spell.Mechanic == WoWSpellMechanic.Incapacitated ||
                     a.Spell.Mechanic == WoWSpellMechanic.Polymorphed ||
                     a.Spell.Mechanic == WoWSpellMechanic.Sapped ||
                     a.Spell.Mechanic == WoWSpellMechanic.Shackled ||
                     a.Spell.Mechanic == WoWSpellMechanic.Asleep ||
                     a.Spell.Mechanic == WoWSpellMechanic.Frozen ||
                     a.Spell.Mechanic == WoWSpellMechanic.Invulnerable ||
                     a.Spell.Mechanic == WoWSpellMechanic.Invulnerable2 ||
                     a.Spell.Mechanic == WoWSpellMechanic.Turned ||
                     a.Spell.Name == "Hex"
                );
        }

        public static bool IsViableForPolymorph(WoWUnit unit)
        {
            if (IsCrowdControlled(unit))
                return false;

            if (unit.CreatureType != WoWCreatureType.Beast && unit.CreatureType != WoWCreatureType.Humanoid)
                return false;

            if (StyxWoW.Me.CurrentTarget != null && StyxWoW.Me.CurrentTarget == unit)
                return false;

            if (!unit.Combat)
                return false;

            if (!unit.IsTargetingMeOrPet && !unit.IsTargetingMyPartyMember)
                return false;

            if (StyxWoW.Me.GroupInfo.IsInParty &&
                StyxWoW.Me.PartyMembers.Any(p => p.CurrentTarget != null && p.CurrentTarget == unit))
                return false;

            return true;
        }

        public static bool ValidUnit(WoWUnit p)
        {
            if (IgnoreMobs.Contains(p.Entry))
                return false;

            // Ignore shit we can't select/attack
            if (!p.CanSelect || !p.Attackable)
                return false;

            // Ignore friendlies!
            if (p.IsFriendly)
                return false;

            // Duh
            if (p.IsDead)
                return false;

            // If its a pet, lets ignore it please.
            if (p.IsPet || p.OwnedByRoot != null)
                return false;

            // And ignore critters/non-combat pets
            return !p.IsNonCombatPet && !p.IsCritter;
        }
    }
}